package com.turbo.cloud.core.utils;

import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;

/**
 * 多因子验证和TOTP
 *
 * @author zhangluning
 */
@Slf4j
public class TotpUtil {
    //public static void main(String[] args) {
    //    String key = "vVgYlufzmEn0yJwDWYjEmyI6tY1UitlywKbOv8nM1nLioDzZEFCedK8+g1YwsDsA0n9vLC/4skzJE6EYQBSIXw==";
    //    String keyStr = generateKey();
    //    System.out.println(keyStr);
    //    try {
    //        String code = createTotp(decodeKeyFromString(key), Instant.now());
    //        System.out.println(code);
    //        System.out.println(validateTotp(decodeKeyFromString(key), code));
    //    } catch (InvalidKeyException e) {
    //        e.printStackTrace();
    //    }
    //}

    /**
     * 密码有效期，在有效期内，生成的所有密码都一样
     */
    private static final long TIME_STEP = 60 * 5L;

    /**
     * 密码长度
     */
    private static final int PASSWORD_LENGTH = 6;
    private static KeyGenerator keyGenerator;
    private static TimeBasedOneTimePasswordGenerator totp;

    /*
     * 初始化代码块，Java 8 开始支持。这种初始化代码块的执行在构造函数之前
     * 准确说应该是 Java 编译器会把代码块拷贝到构造函数的最开始。
     */
    static {
        try {
            totp = new TimeBasedOneTimePasswordGenerator(Duration.ofSeconds(TIME_STEP), PASSWORD_LENGTH);
            keyGenerator = KeyGenerator.getInstance(totp.getAlgorithm());
            // SHA-1 and SHA-256 需要 64 字节 (512 位) 的 key; SHA512 需要 128 字节 (1024 位) 的 key
            keyGenerator.init(512);
        } catch (NoSuchAlgorithmException e) {
            log.error("没有找到算法 {}", e.getLocalizedMessage());
        }
    }

    /**
     * 创建TOTP验证码
     *
     * @param key  用于生成 TOTP 秘钥
     * @param time 用于生成 TOTP 的时间
     * @return 一次性验证码
     * @throws InvalidKeyException 非法 Key 抛出异常
     */
    public static String createTotp(final Key key, final Instant time) throws InvalidKeyException {
        String format = "%0" + PASSWORD_LENGTH + "d";
        return String.format(format, totp.generateOneTimePassword(key, time));
    }

    /**
     * 验证 TOTP
     *
     * @param key  用于生成 TOTP 秘钥
     * @param code 要验证的 TOTP
     * @return 是否一致
     * @throws InvalidKeyException 非法 Key 抛出异常
     */
    public static boolean validateTotp(final Key key, final String code) throws InvalidKeyException {
        Instant now = Instant.now();
        return createTotp(key, now).equals(code);
    }

    /**
     * 生成秘钥
     */
    public static String generateKey() {
        return encodeKeyToString(keyGenerator.generateKey());
    }

    /**
     * 生成秘钥，加密成Base64
     *
     * @param key 随机秘钥
     */
    private static String encodeKeyToString(Key key) {
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }

    /**
     * 解密TOTP验证码
     *
     * @param strKey
     */
    public static Key decodeKeyFromString(String strKey) {
        return new SecretKeySpec(Base64.getDecoder().decode(strKey), totp.getAlgorithm());
    }
}
